﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary.LinearEquationSolvers
{
    /// <summary>
    /// This class groups all available splitting methods to solve real linear systems in an iterative
    /// way.
    /// </summary>
    [Serializable]
    public abstract class AbstractRealSplittingMethod
    {
        /// <summary>
        /// A field, which describes a possible precision error during the calculation.
        /// </summary>
        private bool precisionError;

        /// <summary>
        /// The coefficient matrix to solve.
        /// </summary>
        private readonly LinearEquationSystem equationSystem;

        /// <summary>
        /// Initializes a new instance of the <see cref="AbstractRealSplittingMethod"/> class.
        /// </summary>
        /// <param name="coefficientMatrix">The coefficient matrix of the equation system to solve.</param>
        /// <param name="resultVector">The result vector of the equation system to solve.</param>
        protected AbstractRealSplittingMethod(Matrix coefficientMatrix, GeneralVector resultVector)
        {
            this.precisionError = false;
            this.equationSystem = new LinearEquationSystem(coefficientMatrix, resultVector);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AbstractRealSplittingMethod"/> class.
        /// </summary>
        /// <param name="equationSystem">The equation system to solve.</param>
        protected AbstractRealSplittingMethod(LinearEquationSystem equationSystem)
        {
            this.precisionError = false;
            this.equationSystem = new LinearEquationSystem(equationSystem);
        }

        /// <summary>
        /// Gets a value indicating whether a precision error occurs during the calculation.
        /// </summary>
        /// <value>True if a precision error occurs during the calculation otherwise, false.</value>
        public bool PrecisionError
        {
            get { return precisionError; }
        }

        /// <summary>
        /// Gets or sets the coefficient matrix of the equation system to solve.
        /// </summary>
        /// <value>The coefficient matrix of the equation system to solve.</value>
        public Matrix CoefficientMatrix
        {
            get { return equationSystem.CoefficientMatrix; }
            set { equationSystem.CoefficientMatrix = value; }
        }

        /// <summary>
        /// Gets or sets the result vector of the equation system to solve.
        /// </summary>
        /// <value>The result vector of the equation system to solve.</value>
        public GeneralVector ResultVector
        {
            get { return equationSystem.ResultVector; }
            set { equationSystem.ResultVector = value; }
        }

        /// <summary>
        /// Solves the linear system, which is defined by the coefficient matrix.
        /// </summary>
        /// <returns>The solved linear system.</returns>
        public abstract GeneralVector SolveLinearSystem();

        /// <summary>
        /// Solves the linear system, which is defined by the coefficient matrix.
        /// </summary>
        /// <param name="startValues">The start values of the calculation in form of a vector.</param>
        /// <returns>The solved linear system.</returns>
        public abstract GeneralVector SolveLinearSystem(GeneralVector startValues);

        /// <summary>
        /// Solves the linear system, which is defined by the coefficient matrix.
        /// </summary>
        /// <param name="startValues">The start values of the calculation in form of a vector.</param>
        /// <param name="maxIterations">The maximum of iterations for the calculation.</param>
        /// <returns>The solved linear system.</returns>
        public abstract GeneralVector SolveLinearSystem(GeneralVector startValues, int maxIterations);

        /// <summary>
        /// Solves the linear system, which is defined by the coefficient matrix.
        /// </summary>
        /// <param name="startValues">The start values of the calculation in form of a vector.</param>
        /// <param name="maxIterations">The maximum of iterations for the calculation.</param>
        /// <param name="precision">The precision of the results to calculate.</param>
        /// <returns>The solved linear system.</returns>
        public abstract GeneralVector SolveLinearSystem(GeneralVector startValues, int maxIterations, double precision);

        /// <summary>
        /// Precisions check method for each iteration of the calculation.
        /// </summary>
        /// <param name="firstVector">The first vector.</param>
        /// <param name="secondVector">The second vector.</param>
        /// <param name="precision">The precision to check.</param>
        /// <returns>True if the every component of the vectors fulfill the precision requirements 
        /// otherwise, false.</returns>
        protected bool PrecisionCheck(GeneralVector firstVector, GeneralVector secondVector, double precision)
        {
            if (firstVector.Count != secondVector.Count)
            {
                return false;
            }

            for (int i = 0; i < firstVector.Count; i++)
            {
                if (Math.Abs(firstVector[i] - secondVector[i]) > precision)
                {
                    this.precisionError = true;

                    return false;
                }
            }

            this.precisionError = false;

            return true;
        }

        /// <summary>
        /// Checks if the splitting method surely converge.
        /// </summary>
        /// <returns>True if the splitting method surely converge otherwise, false.</returns>
        public bool CheckConverge()
        {
            bool strictlyGreater = false;

            for (int i = 0; i < this.equationSystem.CoefficientMatrix.Rows; i++)
            {
                double tempuri = 0;
                double compareValue =
                    Math.Abs(this.equationSystem.CoefficientMatrix.GetValueAtPosition(new MatrixPosition(i, i)));

                for (int j = 0; j < this.equationSystem.CoefficientMatrix.Columns; j++)
                {
                    tempuri +=
                        Math.Abs(this.equationSystem.CoefficientMatrix.GetValueAtPosition(new MatrixPosition(i, j)));
                }

                if (compareValue > (tempuri - compareValue))
                {
                    strictlyGreater = true;
                }
                else if (compareValue < (tempuri - compareValue))
                {
                    return false;
                }
            }

            return strictlyGreater;
        }
    }
}